/**
  ******************************************************************************
  * @file           
  * @author         古么宁
  * @brief          时钟同步相关流程实现
  ******************************************************************************
  *
  * COPYRIGHT(c) 2019 GoodMorning
  *
  ******************************************************************************
  */
/* Includes ---------------------------------------------------*/
#include <string.h>

#include "iec10x.h"
#include "iec10x_link.h"
#include "iec10x_debug.h"

#include "../iec/devif.h"

#include "COT.h"

/* Private types ------------------------------------------------------------*/

struct sync_pkt {
	uint8_t len ;
	uint8_t data[1];
};

/* Private macro ------------------------------------------------------------*/
/* Private variables --------------------------------------------------------*/
/* Global  variables --------------------------------------------------------*/
/* Private function prototypes ----------------------------------------------*/


#if (!ENABLE_IEC10x_SLAVE)
	#define iec10xslv_sync_respond(...)
#else

static void iec10xslv_sync_reply(iec10x_t * bus)
{
	struct sync_pkt * syncpkt = (struct sync_pkt *)bus->operate.argv;
	syncpkt->data[IEC_COT] = COT_ACTIVATE_CONFIRM ;

	if (bus->mode & IEC_101) {
		uint8_t lnkctrl = 0 ;
		if (bus->mode & IEC_BALANCE) {
			int FCB = (bus->iec101.FCB == 0) ;
			bus->iec101.FCB = FCB;
			lnkctrl  = IEC_CTRL(0,1,FCB,1,SLAVE_OK);
			lnkctrl |= (bus->mode & IEC_DIRECT)    ;
		}
		else {
			lnkctrl = IEC_CTRL(0,0,0,0,SLAVE_OK);
		}
		iec101_protocol_send(bus,syncpkt->data,syncpkt->len,lnkctrl); 
	}
	else {
		iec104_protocol_send(bus,syncpkt->data,syncpkt->len,0); 
	}

	memset(&bus->operate,0,sizeof(struct iec_do));
}


/**
  * @brief    一问一答式时间同步对钟(通过设置 RTU 参数表中的 ”对时间隔 ”单位是分钟 ,默认是 20 分钟左右 .)
  * @param    bus      : 总线
  * @param    ASDUrx   : 接收到的 ASDU 数据包
  * @param    ASDUlen  : 接收到的 ASDU 数据包长度
  * @return   don't care
*/
static void iec10xslv_sync_respond(iec10x_t * bus , uint8_t * ASDU,int ASDUlen)
{
	IEC_INDEX(bus->causelen,bus->cmnaddrlen);
	uint8_t   lnkctrl;
	uint8_t * data = &ASDU[IEC_INFO + bus->infoaddrlen];
	
	struct sync_pkt  * syncpkt ;
	struct iec_devif * dev ;

	dev = iec_devif_find(&bus->device,C_CS_NA_1);
	if (dev && dev->profn) {
		dev->profn(bus,ASDU[IEC_COT],0,0,data);
	}
	else {
		iec10x_warn(bus,"synchronize interface not found.\r\n");
	}

	/* 暂存当前数据包，回复镜像包，对时报文最长 16 字节 */
	syncpkt = (struct sync_pkt *)bus->operate.argv;
	syncpkt->len = ASDUlen;
	memcpy(syncpkt->data,ASDU,ASDUlen);

	if (bus->mode & IEC_101) {
		if (bus->mode & IEC_BALANCE){
			lnkctrl  = IEC_CTRL(0,0,0,0,SLAVE_OK) ;
			lnkctrl |= (bus->mode & IEC_DIRECT)   ;
			iec101_protocol_send(bus,NULL,0,lnkctrl);
			bus->operate.todo = iec10xslv_sync_reply;
			bus->iec101.todo  = iec10xslv_sync_reply;
		}
		else {
			lnkctrl = (IEC_CTRL(0,0,1,0,SLAVE_OK))  ;
			iec101_protocol_send(bus,NULL,0,lnkctrl);
			bus->operate.todo = iec10xslv_sync_reply;
		}
	}
	else {
		bus->operate.todo = iec10xslv_sync_reply;
	}
}

#endif /** #if (!ENABLE_IEC10x_SLAVE) */


/* ------------------- 主站部分功能 ------------------- */


#if (!ENABLE_IEC10x_MASTER)

	/* 不使能主站功能时 */
	void iec10xmst_sync(iec10x_t * bus)           {}
	void iec10xmst_synchronize_req(iec10x_t * bus){}

#else

void iec10xmst_sync(iec10x_t * bus)
{
	IEC_INDEX(bus->causelen,bus->cmnaddrlen);
	uint8_t * ASDUtx = ASDU_TXBUFF(bus);
	uint16_t  ASDUlen ;

	struct iec_devif * dev = iec_devif_find(&bus->device,C_CS_NA_1); 
	
	if (!dev || !dev->readfn || !dev->argsize) {
		bus->synct = iec_timestamp;
		memset(&bus->operate,0,sizeof(struct iec_do));
		return ;
	}

	ASDUtx[IEC_TYPE]          = C_CS_NA_1               ;
	ASDUtx[IEC_QUALIFIER]     = 0x01                    ;
	ASDUtx[IEC_COT]           = COT_ACTIVATE            ;
	ASDUtx[IEC_COT+1]         = bus->connectaddr & 0xff ;
	ASDUtx[IEC_COMMON_ADDR]   = bus->cmnaddr     & 0xff ;
	ASDUtx[IEC_COMMON_ADDR+1] = bus->cmnaddr     >> 8   ;
	ASDUtx[IEC_INFO]          = 0                       ; 
	ASDUtx[IEC_INFO+1]        = 0                       ;
	ASDUtx[IEC_INFO+2]        = 0                       ;
	
	dev->readfn(bus,0,&ASDUtx[IEC_INFO + bus->infoaddrlen]);      //读取时间到信息结构体位置
	
	ASDUlen = IEC_INFO  + bus->infoaddrlen + dev->argsize;  // ASDU 总长

	if (!(bus->mode & IEC_101)) {
		iec104_protocol_send(bus,ASDUtx,ASDUlen,0);
	}
	else {
		int     FCB = (bus->iec101.FCB == 0) ;
		uint8_t lnkctrl = IEC_CTRL(0,1,FCB,1,MASTER_TRANS);
		bus->iec101.FCB = FCB;
		if (bus->mode & IEC_BALANCE) {
			lnkctrl |= (bus->mode & IEC_DIRECT) ;
		}
		iec101_protocol_send(bus,ASDUtx,ASDUlen,lnkctrl);
	}

	bus->busy |= IEC10x_SYNC;
	bus->synct = iec_timestamp;
}


/**
  * @brief    主机发送对时命令
  * @author   古么宁
  * @param    bus      : 总线
  * @return   don't care 
*/
void iec10xmst_synchronize_req(iec10x_t * bus)
{
	bus->synct = iec_timestamp - bus->sync_period;
}


#endif /** #if (!ENABLE_IEC10x_MASTER) */


/**
  * @brief    时间同步对钟(通过设置 RTU 参数表中的 ”对时间隔 ”单位是分钟 ,默认是 20 分钟左右 .)
  * @param    bus      : 总线
  * @param    ASDUrx   : 接收到的 ASDU 数据包
  * @param    ASDUlen  : 接收到的 ASDU 数据包长度
  * @return   don't care
*/
void synchronize_respond(iec10x_t * bus , uint8_t * ASDU,int ASDUlen)
{
	if (!(bus->mode & IEC_MASTER)) { 
		/* 作为从机时响应主机的同步 */
		iec10xslv_sync_respond(bus,ASDU,ASDUlen);
	}
	else { // 作为主机收到来自从机的对时反校验
		bus->busy &= ~IEC10x_SYNC;
		iec10x_msg(bus,"\t --- synchronize end ---\r\n");
	}
}

